use std::{cell::OnceCell, collections::HashSet, hash::Hash};

use nalgebra::Vector2;
use soukoban::Actions;


use super::solver::Solver;

#[derive(Clone,Eq)]
pub struct State{
    pub player_position: Vector2<i32>,
    pub box_positions: HashSet<Vector2<i32>>,
    pub actions: Actions,
    heuristic: usize,
    lower_bound: OnceCell<usize>,
}

impl PartialEq for State {
    fn eq(&self, other: &Self) -> bool {
        self.player_position == other.player_position && self.box_positions==other.box_positions
    }
}

impl Hash for State {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.player_position.hash(state);
        for position in &self.box_positions {
            position.hash(state);
        }
    }
}

impl Ord for State {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.heuristic.cmp(&other.heuristic).reverse()
    }
}

impl PartialOrd for State {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl State {
    pub fn new(
        player_position: Vector2<i32>,
        box_positions: HashSet<Vector2<i32>>,
        actions: Actions,
        solver: &Solver,
    ) -> Self {
        let mut instance = Self{
            player_position,
            box_positions,
            actions,
            heuristic: 0,
            lower_bound: OnceCell::new(),
        };

        debug_assert!(instance.actions.moves() < 10_000);
        debug_assert!(instance.actions.pushes() < 10_000);
        debug_assert!(instance.lower_bound(solver) < 10_000);

        instance.heuristic = match solver.strategy() {
            Strategy::Fast => instance.lower_bound(solver) * 10_000 + instance.actions.moves(),
            Strategy::Mixed => ,
            Strategy::OptimalMovePush => ,
            Strategy::OptimalPushMove => ,
        }

        instance
    }


    fn lower_bound(&self,solver: &Solver) -> usize {
        *self.lower_bound.get_or_init(|| self.calculate_lower_bound(solver))
    }

    fn calculate_lower_bound(&self,solver: &Solver) -> usize {
        let mut sum: usize = 0;
        for box_position in &self.box_positions {
            match solver.lower_bounds().get(box_position) {
                Some(lower_bound) => sum += lower_bound,
                None => return 10_000 -1,
            }
        }
        sum
    }
}